class Ecosystem {
  
  Statevar[] vars = new Statevar[0];
  Flux[] fluxes = new Flux[0];
  FreeParam[] params = new FreeParam[0];
  int Ncells;
  Statevar time;  
  
  // the constructor for classes that extend this one should define the stock-flux network and free params, but not assign or read values, or allocate space:
  // that will be done by the constructor for the Environment
  
  Ecosystem() {}
  
  void allocate(int N) {
    Ncells = N;
    for (int i=0; i<vars.length; i++) vars[i].allocate(N);
    for (int i=0; i<fluxes.length; i++) fluxes[i].allocate(N);
  }
  
  Statevar addVar(String shortname) {return addVar(shortname, shortname, "");}
  Statevar addVar(String shortname, String units) {return addVar(shortname, shortname, units);}
  Statevar addVar(String name, String shortname, String units) { // by default, positive-definite and subject to physical dynamics
    Statevar V = new Statevar(name, shortname, units, Ncells);
    V.advects = true;
    V.mixes = true;
    V.sinks = false;
    V.min = 0;
    vars = (Statevar[])append(vars,V);
    V.hidden = false;
    return V;
  }
    
  Statevar addGhostVar(String shortname) {return addGhostVar(shortname, shortname, "");}
  Statevar addGhostVar(String shortname, String units) {return addGhostVar(shortname, shortname, units);}
  Statevar addGhostVar(String name, String shortname, String units) { // by default, unbounded and not subject to physical forces
    Statevar V = addVar(name, shortname, units);
    V.advects = false;
    V.mixes = false;
    V.sinks = false;
    V.hidden = true;
    V.min = -Inf;
    return V;
  }
 
  Flux addFlux(String shortname, Statevar from, Statevar to) {return addFlux(shortname, shortname, from, to);}
  Flux addFlux(String name, String shortname, Statevar from, Statevar to) {
    Flux F = new Flux(name,shortname,from,to);
    fluxes = (Flux[])append(fluxes,F);
    return F;
  }
  
  FreeParam addParam(String shortname, float initial) {return addParam(shortname, shortname, "", initial, initial/100, initial*100);}
  FreeParam addParam(String shortname, String units, float initial) {return addParam(shortname, shortname, units, initial, initial/100, initial*100);}
  FreeParam addParam(String name, String shortname, String units, float initial) {return addParam(name, shortname, units, initial, initial/100, initial*100);}
  FreeParam addParam(String name, String shortname, String units, float initial, float min, float max) {
    FreeParam P = new FreeParam(name,shortname,units,initial,min,max);
    params = (FreeParam[])append(params,P);
    return P;
  }
  
  // arrays of fluxes

  Flux[] addFluxSet(String name, String shortname, Statevar[] from, Statevar to) {
    Flux[] F = new Flux[from.length];
    for (int i=0; i<F.length; i++) F[i] = addFlux(name + "-" + i, shortname + "-" + i, from[i], to);
    return F;
  }

  Flux[] addFluxSet(String name, String shortname, Statevar from, Statevar[] to) {
    Flux[] F = new Flux[to.length];
    for (int i=0; i<F.length; i++) F[i] = addFlux(name + "-" + i, shortname + "-" + i, from, to[i]);
    return F;    
  }

  Flux[][] addFluxSet2D(String name, String shortname, Statevar from[], Statevar[] to) {
    Flux[][] F = new Flux[from.length][to.length];
    for (int i=0; i<from.length; i++) {
      for (int j=0; j<to.length; j++) {
        F[i][j] = addFlux(name + "-" + i + "-" + j, shortname + "-" + i + "-" + j, from[i], to[j]);
      }
    }
    return F;    
  }

  Statevar getVar(String S) {
    for (int i=0; i<vars.length; i++) if (vars[i].shortname.equals(S)) return vars[i];
    return null;
  }
  
  Flux getFlux(String S) {
    for (int i=0; i<fluxes.length; i++) if (fluxes[i].shortname.equals(S)) return fluxes[i];
    return null;
  }  
  
  FreeParam getParam(String S) {
    for (int i=0; i<params.length; i++) if (params[i].shortname.equals(S)) return params[i];
    return null;
  }
  
  int getVarIndex(String S) {
    for (int i=0; i<vars.length; i++) if (vars[i].shortname.equals(S)) return i;
    return -1;
  }
  
  int getFluxIndex(String S) {
    for (int i=0; i<fluxes.length; i++) if (fluxes[i].shortname.equals(S)) return i;
    return -1;
  }  
  
  int getParamIndex(String S) {
    for (int i=0; i<params.length; i++) if (params[i].shortname.equals(S)) return i;
    return -1;
  }

  float getParamVal(String S) {
    for (int i=0; i<params.length; i++) if (params[i].shortname.equals(S)) return params[i].current;
    return 0./0;
  }
  
  Object get(String S) {
    Statevar V = getVar(S);
    if (V != null) return V;
    FreeParam P = getParam(S);
    if (P != null) return P;
    return getFlux(S);
  }
    


  void updatePrivateParams() {} // any internal bookkeeping when the user changes a param
  
  void calcFluxes() {} // put the model ODEs here
  
 
}
